/**
 * 计算属性：解决模板中复杂的逻辑运算的问题
 * 计算属性只在内部逻辑依赖的数据发生变化的时候才会被再次调用
 * 计算属性会缓存其依赖的上一次计算出的数据结果
 * 多次复用一个相同的数据，计算属性只调用一次
 */

var Vue = (function () {

  /**
   * 需要与 vm.data 隔离
   * value: 函数执行结果
   * get: get
   * dep: [a, b] 依赖
   */
  var _computedData = {},
    reg_data = /\{\{(.+?)\}\}/g,
    dataPool = {}

  var Vue = function (options) {
    this.$el = document.querySelector(options.el)
    this.$data = options.data()

    this._init(this, options.computed, options.template)
  }

  Vue.prototype = {
    _init: function (vm, computed, template) {
      this.reactive(vm, computed)
      this.render(vm, template)
    },
    /**
     * 数据响应处理
     * @param {*} vm 
     * @param {*} computed 
     */
    reactive: function (vm, computed) {
      this.dataReactive(vm)
      this.computedReactive(vm, computed)
    },
    dataReactive(vm) {
      const _data = vm.$data

      for (var key in _data) {
        (function (keyName) {
          Object.defineProperty(vm, keyName, {
            get() {
              return _data[keyName]
            },
            set(newData) {
              _data[keyName] = newData
              this._updateComputedData(vm, keyName, function (key) {
                vm._renderUpdate(vm, key)
              })
              this._renderUpdate(vm, keyName)
            }
          })
        }(key))
      }
    },
    computedReactive(vm, computed) {
      this._initComputedData(vm, computed)

      for (var key in _computedData) {
        (function (keyName) {
          Object.defineProperty(vm, keyName, {
            get() {
              return _computedData[keyName].value
            },
            set(newValue) {
              _computedData[key].setter.call(vm, newValue)
              _computedData[key].value = newValue
            }
          })
        }(key))
      }
    },
    /**
     * 初始化计算属性
     * @param {*} vm 
     * @param {*} computed 
     */
    _initComputedData(vm, computed) {
      for (var key in computed) {
        var descriptor = Object.getOwnPropertyDescriptor(computed, key),
          descriptorGetterFn = descriptor.value.get
            ? descriptor.value.get
            : descriptor.value,
          descriptorSetterFn = descriptor.value.set

        _computedData[key] = {}
        _computedData[key].value = descriptorGetterFn.call(vm) // computed中的 更改 this 指向
        _computedData[key].getter = descriptorGetterFn
        _computedData[key].dep = this._collectDep(descriptorGetterFn)

        descriptorSetterFn && (_computedData[key].setter = descriptorSetterFn)
      }
    },
    /**
     * 收集 computer 中的依赖
     * @param {*} fn 
     */

    _collectDep(fn) {
      var _collection = fn.toString().match(/this.(.+?)/g)

      if (_collection.length > 0) {
        for (var idx in _collection) {
          _collection[idx] = _collection[idx].split('.')[1]
        }
      }

      return _collection
    },
    render: function (vm, template) {
      // 需要编译模板 _compileTemplate

      // 如果不用虚拟节点，就需要真实节点
      var container = document.createElement('div'),
        _el = vm.$el


      container.innerHTML = template

      var domTree = this._compileTemplate(vm, container)

      _el.appendChild(domTree)
    },
    /**
     * 模板解析
     * @param {*} vm 
     * @param {*} container 
     */
    _compileTemplate(vm, container) {
      var allNodes = container.getElementsByTagName('*'),
        nodeItem = null

      for (var i = 0; i < allNodes.length; i++) {
        nodeItem = allNodes[i]

        nodeItem.textContent = nodeItem.textContent.replace(reg_data, function (node, key) {
          dataPool[key.trim()] = nodeItem
          return vm[key.trim()]
        })
      }

      return container
    },
    _renderUpdate(vm, key) {
      dataPool[key] && (dataPool[key].textContent = vm[key])
    },
    /**
     * 依赖变更 更新 计算属性computed
     * @param {*} vm 
     * @param {*} key 依赖名称
     * @param {*} updateFn 更新后回调
     */
    _updateComputedData(vm, key, updateFn) {
      var _dep = null

      for (var _key in _computedData) { //_key: total testGet
        _dep = _computedData[_key].dep

        for (var i = 0; i < _dep.length; i++) {
          if (_dep[i] === key) {
            _computedData[_key].value = _computedData[_key].getter.call(vm)
            updateFn(_key)
          }
        }
      }
    }
  }

  return Vue
})()

export default Vue
